#!/usr/bin/python
# -*- coding: utf-8 -*-

# This file is part of Cockpit.
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import parent
from testlib import *
from storagelib import *

class TestStorage(StorageCase):
    def tearDown(self):
        # HACK - for debugging https://github.com/cockpit-project/cockpit/issues/2924
        print self.machine.execute("parted -s /dev/md127 print || true")
        print self.machine.execute("lsblk || true")
        MachineCase.tearDown(self)

    def wait_states(self, states):
        self.browser.wait_js_func("""(function(states) {
          var found = { };
          $('#disks tr').each(function (i, tr) {
            var cell = $(tr).find('td:nth-child(2)');
            var name = $(cell).find('a').text();
            var state = $(cell).find('span:nth-child(3)').text();
            found[name] = state;
          });
          for (s in states) {
            if (states[s] != found[s])
              return false;
          }
          return true;
        })""", states)

    def raid_add_disk(self, name):
        self.browser.click('[data-action="mdraid_add_disk"]')
        self.dialog_wait_open()
        self.dialog_select("disks", name, True)
        self.dialog_apply()
        self.dialog_wait_close()

    def raid_remove_disk(self, name):
        self.browser.click('#disks tr:contains("%s") button' % name)

    def raid_action(self, action):
        self.browser.click_action_btn ('.panel-heading:contains("RAID Device") .btn-group', action)

    def raid_default_action(self, action):
        self.browser.wait_action_btn ('.panel-heading:contains("RAID Device") .btn-group', action)
        self.browser.click_action_btn ('.panel-heading:contains("RAID Device") .btn-group')

    def testRaid(self):
        m = self.machine
        b = self.browser

        self.login_and_go("/storage")
        b.wait_in_text("#drives", "VirtIO")

        # Add four disks and make a RAID out of three of them
        m.add_disk("50M", serial="DISK1")
        m.add_disk("50M", serial="DISK2")
        m.add_disk("50M", serial="DISK3")
        m.add_disk("50M", serial="DISK4")
        b.wait_in_text("#drives", "DISK1")
        b.wait_in_text("#drives", "DISK2")
        b.wait_in_text("#drives", "DISK3")
        b.wait_in_text("#drives", "DISK4")

        b.click("#create-mdraid")
        self.dialog_wait_open()
        self.dialog_wait_val("level", "raid5")
        self.dialog_apply()
        self.dialog_wait_error("disks", "At least 2 disks are needed")
        self.dialog_select("disks", "DISK1", True)
        self.dialog_apply()
        self.dialog_wait_error("disks", "At least 2 disks are needed")
        self.dialog_select("disks", "DISK2", True)
        self.dialog_select("disks", "DISK3", True)
        self.dialog_set_val("level", "raid6")
        self.dialog_apply()
        self.dialog_wait_error("disks", "At least 4 disks are needed")
        self.dialog_set_val("level", "raid5")
        self.dialog_set_val("name", "ARR")
        self.dialog_apply()
        self.dialog_wait_close()
        b.wait_in_text("#mdraids", "ARR")

        b.click('#mdraids tr:contains("ARR")')
        b.wait_visible('#storage-detail')

        self.wait_states({ "QEMU QEMU HARDDISK (DISK1)": "In Sync",
                           "QEMU QEMU HARDDISK (DISK2)": "In Sync",
                           "QEMU QEMU HARDDISK (DISK3)": "In Sync" })

        # Degrade and repair it

        def info_field(name):
            return '#detail-header tr:contains("%s") td:nth-child(2)' % name

        def wait_degraded_state(is_degraded):
            degraded_selector = ".alert span:contains('The RAID Array is in a degraded state')"
            if is_degraded:
                b.wait_present(degraded_selector)
                b.wait_visible(degraded_selector)
            else:
                b.wait_not_present(degraded_selector)


        # The preferred device should be /dev/md/ARR, but a freshly
        # created array seems to have no symlink in /dev/md/.
        #
        # See https://bugzilla.redhat.com/show_bug.cgi?id=1397320
        #
        dev = b.text(info_field("Device"))

        m.execute("mdadm --quiet %s --fail /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_DISK1 --remove /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_DISK1" % dev)
        wait_degraded_state(True)
        m.execute("wipefs -a /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_DISK1")
        m.execute("mdadm --quiet %s --add /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_DISK1" % dev)
        wait_degraded_state(False)

        # Turn it off and on again
        self.raid_default_action("Stop")
        b.wait_in_text(info_field("State"), "Not running")
        self.raid_default_action("Start")
        b.wait_in_text(info_field("State"), "Running")

        # Stopping and starting the array will create the expected
        # symlink in /dev/md/.
        #
        b.wait_text(info_field("Device"), "/dev/md/ARR")

        # Partitions also get symlinks in /dev/md/, so they should
        # have names like "/dev/md/ARR1".  Existing storaged versions
        # don't pick them up as the preferred device, however, so we
        # will see "/dev/md127p1", for example.
        #
        # (https://github.com/storaged-project/storaged/issues/98)
        #
        part_prefix = dev + "p"

        # Create partition table
        b.click('button:contains(Create partition table)')
        self.dialog({ "type": "gpt" })
        self.content_row_wait_in_col(1, 1, "Free Space")

        # Create first partition
        self.content_row_action(1, "Create Partition")
        self.dialog({ "size": 20,
                      "type": "ext4",
                      "name": "One" })
        self.content_row_wait_in_col(1, 1, "ext4 File System")
        self.content_row_wait_in_col(1, 2, part_prefix + "1")

        # Create second partition
        self.content_row_action(2, "Create Partition")
        self.dialog({ "type": "ext4",
                      "name": "Two" })

        self.content_row_wait_in_col(2, 1, "ext4 File System")
        self.content_row_wait_in_col(2, 2, part_prefix + "2")
        b.wait_not_in_text("#detail-content", "Free Space")

        # Play with disks

        # Add a spare
        self.raid_add_disk("DISK4")
        self.wait_states({ "QEMU QEMU HARDDISK (DISK4)": "Spare" })

        # Remove DISK1.  The spare takes over.
        self.raid_remove_disk("DISK1")
        b.wait_not_in_text('#disks', "DISK1")
        self.wait_states({ "QEMU QEMU HARDDISK (DISK4)": "In Sync" })

        # Remove DISK4.  The array degrades.
        self.raid_remove_disk("DISK4")
        b.wait_not_in_text('#disks', "DISK4")
        wait_degraded_state(True)

        # Add DISK1.  The array recovers.
        self.raid_add_disk("DISK1")
        b.wait_in_text('#disks', "DISK1")
        wait_degraded_state(False)

        # Stop the array, destroy a disk, and start the array
        self.raid_default_action("Stop")
        b.wait_in_text(info_field("State"), "Not running")
        m.execute("wipefs -a /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_DISK1")
        b.wait_not_in_text('#disks', "DISK1")
        self.raid_default_action("Start")
        b.wait_in_text(info_field("State"), "Running")
        wait_degraded_state(True)

        # Add DISK1.  The array recovers.
        self.raid_add_disk("DISK1")
        b.wait_in_text('#disks', "DISK1")
        wait_degraded_state(False)

        # Delete the array.  We are back on the storage page.
        self.raid_action("Delete")
        self.confirm()
        with b.wait_timeout(120):
            b.wait_visible("#storage")
            b.wait_not_in_text ("#mdraids", "ARR")

    def testNoRemovingLastDisk(self):
        m = self.machine
        b = self.browser

        self.login_and_go("/storage")
        b.wait_in_text("#drives", "VirtIO")

        m.add_disk("50M", serial="DISK1")
        m.add_disk("50M", serial="DISK2")
        b.wait_in_text("#drives", "DISK1")
        b.wait_in_text("#drives", "DISK2")

        b.click("#create-mdraid")
        self.dialog_wait_open()
        self.dialog_set_val("level", "raid5")
        self.dialog_select("disks", "DISK1", True)
        self.dialog_select("disks", "DISK2", True)
        self.dialog_set_val("name", "ARR")
        self.dialog_apply()
        self.dialog_wait_close()
        b.wait_in_text("#mdraids", "ARR")

        b.click('#mdraids tr:contains("ARR")')
        b.wait_visible('#storage-detail')

        self.wait_states({ "QEMU QEMU HARDDISK (DISK1)": "In Sync",
                           "QEMU QEMU HARDDISK (DISK2)": "In Sync" })


        self.raid_remove_disk("DISK1")
        b.wait_not_in_text('#disks', "DISK1")

        # The last disk should have the removal button disabled.
        b.wait_present('#disks tr:contains(DISK2) button[disabled]')

if __name__ == '__main__':
    test_main()
